// 日志落地模块的实现
// 1. 抽象落地基类
// 2. 派生子类（根据不同的落地方向进行派生）
// 3. 使用工厂模式进行创建与表示的分离
#ifndef __M_SINK_H__
#define __M_SINK_H__

#include "util.hpp"
#include <memory>
#include <fstream>
#include <sstream>
#include <cassert>

namespace mylog {
    class LogSink {
        public:
            using ptr = std::shared_ptr<LogSink>;
            LogSink() {}
            virtual ~LogSink() {}
            virtual void log(const char *data, size_t len) = 0;
    };
    // 落地方向：标准输出
    class StdoutSink : public LogSink {
        public:
            void log(const char *data, size_t len) {
                std::cout.write(data, len);
            }
    };
    //落地方向：指定文件
    class FileSink : public LogSink {
        public:
            //构造时传入文件名，并打开文件，将操作句柄管理起来
            FileSink(const std::string &pathname):_pathname(pathname) {
                // 1. 创建日志文件所在的目录
                util::File::creatDirectory(util::File::path(pathname));
                // 2. 创建并打开日志文件
                _ofs.open(_pathname, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
            }
            //将日志消息写入到标准输出
            void log(const char *data, size_t len) {
                _ofs.write(data, len);
                assert(_ofs.good());
            }
        private:
            std::string _pathname;
            std::ofstream _ofs;
    };
    //落地方向：滚动文件（以大小进行滚动）
    class RollBySizeSink : public LogSink {
        public:
            // 构造时传入文件名，并打开文件，将操作句柄管理起来
            RollBySizeSink(const std::string &basename, size_t max_size):
                _basename(basename),
                _max_fsize(max_size),
                _cur_fsize(0),
                _namecount(0) {
                    std::string pathname = creatNewFile();
                    // 1. 创建日志文件所在的目录
                    util::File::creatDirectory(util::File::path(pathname));
                    // 2. 创建并打开日志文件
                    _ofs.open(pathname, std::ios::binary | std::ios::app);
                    assert(_ofs.is_open());
                }
            // 将日志消息写入到标准输出--写入前判断文件大小，超过了最大大小就要切换文件
            void log(const char *data, size_t len) {
                if (_cur_fsize >= _max_fsize) {
                    _ofs.close();
                    std::string pathname = creatNewFile();
                    _ofs.open(pathname, std::ios::binary | std::ios::app);
                    assert(_ofs.is_open());
                    _cur_fsize = 0;
                }
                _ofs.write(data, len);
                assert(_ofs.good());
                _cur_fsize += len;
            }
        private:
            // 进行大小切换，超过指定大小则创建新文件
            std::string creatNewFile() {
                //获取系统时间，以时间来构造问价扩展名
                time_t t = util::Date::now();
                struct tm lt;
                localtime_r(&t, &lt);
                std::stringstream filename;
                filename << _basename;
                filename << lt.tm_year+1900;
                filename << lt.tm_mon+1;
                filename << lt.tm_mday;
                filename << lt.tm_hour;
                filename << lt.tm_min;
                filename << lt.tm_sec;
                filename << "-";
                filename << _namecount++;
                filename << ".log";
                return filename.str();
            }
        private:
            // 防止1s中写出多个log文件
            size_t _namecount;
            //通过基础文件名 + 扩展文件名（以时间生成）组成一个实际的当前输出文件名
            std::string _basename; // ./log/base- -> ./log/base-20023456513.log
            std::ofstream _ofs;
            size_t _max_fsize; // 记录最大大小，当前文件超过了这个大小就要切换文件
            size_t _cur_fsize; // 记录当前文件已经写入的数据大小
    };

    //落地方向：以时间段作为日志文件滚动切换类型
    // 以当前系统时间，取模时间段大小，可以得到当前时间段是第几个时间段
    // 每次以当前系统时间取模，判断与当前文件的时间段是否一致，不一致代表不是同一个时间段
    enum class TimeGap {
        GAP_SECOND,
        GAP_MINUTE,
        GAP_HOUR,
        GAP_DAY,
    };

    class RollByTimeSink : public mylog::LogSink {
        public:
            RollByTimeSink(const std::string &basename, TimeGap gap_type):
                _basename(basename) {
                    switch (gap_type)
                    {
                    case TimeGap::GAP_SECOND: _gap_size = 1; break;
                    case TimeGap::GAP_MINUTE: _gap_size = 60; break;
                    case TimeGap::GAP_HOUR: _gap_size = 3600; break;
                    case TimeGap::GAP_DAY: _gap_size = 3600*24; break;
                    }
                    _cur_gap = _gap_size == 1? util::Date::now() : util::Date::now() % _gap_size; // 获取当前时间段
                    std::string filename = createNewFile();
                    util::File::creatDirectory(util::File::path(filename));
                    _ofs.open(filename, std::ios::binary | std::ios::app);
                    assert(_ofs.is_open());

                }
            // 将日志消息写到标准输出，判断当前时间是否是当前文件的时间段，不是则切换文件
            void log(const char *data, size_t len) {
                time_t cur = util::Date::now();
                if((cur %_gap_size) != _cur_gap) {
                    _ofs.close();
                    std::string filename = createNewFile();
                    _ofs.open(filename, std::ios::binary | std::ios::app);
                    assert(_ofs.is_open());
                }
                _ofs.write(data, len);
                assert(_ofs.good());
            }
        private:
            std::string createNewFile() {
                time_t t = util::Date::now();
                struct tm lt;
                localtime_r(&t, &lt);
                std::stringstream filename;
                filename << _basename;
                filename << lt.tm_year+1900;
                filename << lt.tm_mon+1;
                filename << lt.tm_mday;
                filename << lt.tm_hour;
                filename << lt.tm_min;
                filename << lt.tm_sec;
                filename << ".log";
                return filename.str();
            }
        private:
            std::string _basename;
            std::ofstream _ofs;
            size_t _cur_gap; // 当前是第几个时间段
            size_t _gap_size; // 时间段的大小
    };


    
    class SinkFactory {
        public:
            template <typename SinkType, typename ...Args>  
            static LogSink::ptr create(Args &&...args) {
                return std::make_shared<SinkType>(std::forward<Args>(args)...);
            }
    };
}

#endif